﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PSE.AuditLogging.LogEntry;
using PSE.AuditLogging.Repository;
using PSE.AuditLogging.Client.Configuration;
using System.Diagnostics;
using System.Configuration;
using PSE.AuditLogging.Common.Exception;
using PSE.AuditLogging.Repository.Manager;
using PSE.Framework.ProviderLoader.Configuration;
using PSE.AuditLogging.Common.Configuration;
using PSE.Framework.ErrorLogging;

namespace PSE.AuditLogging.Client
{
    internal class LogStoreRouter : IDisposable
    {
        private object _syncRoot;
        private Dictionary<string, TemporaryRepositoryManager> _temporaryRepositories;
        private Dictionary<string, List<ContingencyRepositoryManager>> _contingencyRepositories;

        internal LogStoreRouter()
        {
            _syncRoot = new object();
            _temporaryRepositories = new Dictionary<string, TemporaryRepositoryManager>();
            _contingencyRepositories = new Dictionary<string, List<ContingencyRepositoryManager>>();

            LoadStorageProviders();
        }

        private void LoadStorageProviders()
        {
            List<ContingencyRepositoryManager> contingencyRepositories;

            lock (_syncRoot)
            {
                try
                {
                    TemporaryRepositoryManager temporaryRepository = null;
                    ContingencyRepositoryManager contingencyRepository = null;

                    //carregar os providers de armazenamento temporario e de contingencia configurados
                    foreach (StorageConfigurationElement storage in ConfigurationReader.LogClientConfiguration.Storages)
                    {
                        contingencyRepositories = new List<ContingencyRepositoryManager>();

                        //carregar o provider de acordo com a configuracao
                        temporaryRepository = new TemporaryRepositoryManager();
                        temporaryRepository.LoadProvider(storage.TemporaryRepositoryProvider);


                        if (storage.ContingencyRepositoriesProviders != null && storage.ContingencyRepositoriesProviders.Count > 0)
                        {
                            //varrer as configuracoes e carregar os providers
                            foreach (ProviderConfigurationElement item in storage.ContingencyRepositoriesProviders)
                            {
                                contingencyRepository = new ContingencyRepositoryManager();
                                contingencyRepository.LoadProvider(item);

                                //adicioanar na lista de repositorios de contingencia do storage 
                                contingencyRepositories.Add(contingencyRepository);
                            }
                        }
                        else
                        {
                            //se nenhum repositorio de contingenci foi configurado
                            throw new ConfigurationErrorsException("The contingency repository must be configured.");
                        }

                        //adicionar uma entrada no dicionario para cada tipo de log, ou seja caso hajam diversos tipos
                        //de log configurados, todos apontarao para as mesmas instancias de repositorio temporario e de contingencia
                        foreach (LogTypeConfigurationElement logType in storage.LogTypes)
                        {
                            _temporaryRepositories.Add(logType.Id, temporaryRepository);
                            _contingencyRepositories.Add(logType.Id, contingencyRepositories);
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new LogWriterException("Unable to create the temporary respository and contingency repository, see inner exception for more details.", ex);
                }
            }
        }

        internal void SendPackage(LogEntryPackage entryPackage)
        {
            SendPackage(entryPackage, false);
        }

        internal void ResendPackage(LogEntryPackage entryPackage)
        {
            SendPackage(entryPackage, true);
        }


        private void SendPackage(LogEntryPackage entryPackage, bool resending)
        {
            #region recuperar os componentes de repositorio sob demanda
            TemporaryRepositoryManager temporaryRepository = null;
            List<ContingencyRepositoryManager> contingencyRepositories = null;


            string logType;
            logType = entryPackage.Body.LogType;

            //verificar se foi informado um tipo de log
            if (string.IsNullOrEmpty(logType))
                throw new ArgumentNullException("LogType", "LogType of the log entry must be informed.");


            //verificar se o tipo esta na lista de tipos configurados
            if (!_temporaryRepositories.ContainsKey(logType) &&
                !_contingencyRepositories.ContainsKey(logType))
            {
                throw new InvalidLogTypeException(string.Format("The logType '{0}' its not configured.", logType));
            }

            temporaryRepository = _temporaryRepositories[logType];
            contingencyRepositories = _contingencyRepositories[logType];


            #endregion

            try
            {
                temporaryRepository.Send(entryPackage);
            }
            catch (Exception ex)
            {
                //se nao for um reenvio
                if (!resending)
                {
                    //apenas logar q houve erro e tentar armazenar no repositorio de contingencia
                    ErrorLogger.WriteLog(PSE.Framework.Configuration.Components.SystemComponents.System, PSE.Framework.Configuration.Components.SystemComponents.AuditLoggingClient, LogType.Warning, ex);

                    bool executed = false;
                    //varrer os repositorios de contigencia tentando enviar
                    foreach (ContingencyRepositoryManager item in contingencyRepositories)
                    {
                        try
                        {
                            item.Send(entryPackage);
                            executed = true;
                            break;
                        }
                        catch (Exception ex1)
                        {
                            //apenas logar o erro e tentar o proximo provider
                            ErrorLogger.WriteLog(PSE.Framework.Configuration.Components.SystemComponents.System, PSE.Framework.Configuration.Components.SystemComponents.AuditLoggingClient, LogType.Warning, ex1);
                        }
                    }

                    //caso n consiga enviar para nenhum repositorio de contingencia
                    if (!executed)
                        throw new LogWriterException("Unable to write log entry on temporary and contingency repository, the log entry will be lost.");
                }
                else//caso n utilize a contingencia, repassar o erro
                    throw;
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (_temporaryRepositories != null)
            {
                foreach (KeyValuePair<string, TemporaryRepositoryManager> item in _temporaryRepositories)
                {
                    if (item.Value != null)
                        item.Value.Dispose();
                }
                _temporaryRepositories = null;
            }

            if (_contingencyRepositories != null)
            {

                foreach (KeyValuePair<string, List<ContingencyRepositoryManager>> item in _contingencyRepositories)
                {
                    if (item.Value != null)
                    {
                        foreach (ContingencyRepositoryManager item1 in item.Value)
                            item1.Dispose();
                    }
                }

                _contingencyRepositories = null;
            }

        }

        #endregion
    }
}
